<!--
 * @Descripttion: 
 * @Author: zheng xinyi
 * @Date: 2024-12-18 10:09:44
 * @LastEditors: zheng xinyi
 * @LastEditTime: 2024-12-19 20:27:31
-->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Cesium 3Dtiles Demo</title>
  <script src="https://cesium.com/downloads/cesiumjs/releases/1.103/Build/Cesium/Cesium.js"></script>
  <style>
    @import url(https://cesium.com/downloads/cesiumjs/releases/1.103/Build/Cesium/Widgets/widgets.css);

    html,
    body,
    #cesiumContainer {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
      overflow: hidden;
    }
  </style>
</head>

<body>
  <div id="cesiumContainer"></div>
  <script>
    const { Cartesian3,
      Matrix3,
      Matrix4,
      Transforms,
      Cesium3DTileset, } = Cesium
    const CesiumMath = Cesium.Math
    // 初始化Cesium Viewer
    Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI2Y2M3ZGQ1Yy1hNzkxLTRhNzAtYThmNy1lMTdiYmQyNmNlOGIiLCJpZCI6Mjc3MDEsImlhdCI6MTczNDU3OTUzNH0.L42oi_l2QOJ3At4woANmZ-TBCLaHP5wT0mn31mgdkeg'
    const viewer = new Cesium.Viewer("cesiumContainer", {
      shadows: false,
      fullscreenButton: false, // 隐藏界面右下角全屏按钮
      homeButton: false, // 隐藏界面右上角初始化地球位置按钮
      animation: false, // 隐藏界面左下角控制动画的面板
      geocoder: false, //右上角 搜索
      sceneModePicker: false, // 右上角 2D/3D切换
      baseLayerPicker: true, // 隐藏界面左上角地图底图的切换按钮
      navigationHelpButton: false, //右上角 Help
      shouldAnimate: true,
      selectionIndicator: false, //隐藏双击entity时候的聚焦框
      skyAtmosphere: false, //去除地球外侧光圈
      infoBox: false, // 点击地球后的信息框
      timeline: false // 隐藏正下方时间线
    });

    viewer._cesiumWidget._creditContainer.style.display = "none";  // 隐藏cesium ion
    viewer.scene.globe.depthTestAgainstTerrain = true


    const tileset = new Cesium.Cesium3DTileset({
      url: "./3dtiles1/tileset.json",
    });

    tileset.readyPromise
      .then(function (tileset) {
        viewer.scene.primitives.add(tileset);
        viewer.zoomTo(tileset)
        customShader();
        // window.tileset = tileset
        // let longitude = 104.98680
        // let latitude = 32.20795
        // let height = 100
        // let modelMatrix = moveModel(tileset, longitude, latitude, height)
        // tileset.modelMatrix = modelMatrix;//移动模型
        // // 创建圆形包围盒
        // let boundingSphere = new Cesium.BoundingSphere(
        //   tileset.boundingSphere.center,
        //   tileset.boundingSphere.radius
        // );
        // //飞向该包围盒
        // viewer.camera.flyToBoundingSphere(boundingSphere);
        // viewer.zoomTo(tileset)
      })
      .catch(function (error) {
        console.log(error);
      });



    function customShader() {
      let customShader = new Cesium.CustomShader({
        // lightingModel: Cesium.LightingModel.UNLIT,
        //  lightingModel: Cesium.LightingModel.PBR,
        //设置变量，由顶点着色器传递给片元着色器
        varyings: {
          v_normalMC: Cesium.VaryingType.VEC3,
          v_st: Cesium.VaryingType.VEC3
        },
        //外部传给顶点着色器或者片元着色器
        uniforms: {
          u_texture: {
            value: new Cesium.TextureUniform({
              url: "./wall.jpg"
            }),
            type: Cesium.UniformType.SAMPLER_2D
          },
          u_texture1: {
            value: new Cesium.TextureUniform({
              url: "./wall1.jpg"
            }),
            type: Cesium.UniformType.SAMPLER_2D
          }
        },
        //贴纹理
        //顶点着色器
        //将法向量从顶点着色器设置变量传给片元着色器
        vertexShaderText: `
                    void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput) {
                            v_normalMC = vsInput.attributes.normalMC;
                            v_st=vsInput.attributes.positionMC ;   
                }`,
        //片元着色器
        fragmentShaderText: `
                    void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
                        vec3 positionMC = fsInput.attributes.positionMC;
                        //这里是设置要贴图的图片的尺寸，设置小了会重复
                        float width = 30.0;
                        float height = 70.0;
                        vec3 rgb;
                        //这是是设置了屋顶的颜色，当和法向量平行时，就是屋顶，这里设置0.95，相当于垂直，建筑物四周开始贴图
                        if (dot(vec3(0.0, 0.0, 1.0), v_normalMC) > 0.95) {
                            material.diffuse = vec3(0.65, 0.65, 0.65);
                        } else {
                            float textureX = 0.0;
                            float dotYAxis = dot(vec3(0.0, 1.0, 0.0), v_normalMC);
                            // cos(45deg) 约等于 0.71，这里是建筑物四周的向量与法向量会大于四十五度夹角
                            if (dotYAxis > 0.71 || dotYAxis < -0.71) {
                            //x代表的是前后面
                                textureX = mod(positionMC.x, width) / width;
                            } else {
                            //z代表的是左右面
                                textureX = mod(positionMC.y, width) / width;
                            }
                            float textureY = mod(positionMC.z, height) / height;
                            //我这里是根据建筑物高度贴了两张不同的图片
                            if (positionMC.z > 105.0) {
                                rgb = texture(u_texture, vec2(textureX, textureY)).rgb;       
                            } else {
                                rgb = texture(u_texture1, vec2(textureX, textureY)).rgb;
                            }
                            
                            material.diffuse = rgb;
                        }
                    }`
      })

      tileset.customShader = customShader
    }


    /**基于本地的ENU坐标系的偏移，也就是垂直于地表向上为Z，东为X，北为Y
     * @param tileset Cesium3DTileset
     * @param dx x轴偏移量。单位：米
     * @param dy y轴偏移量。单位：米
     * @param dz z轴偏移量。单位：米
     */
    function translate(tileset, dx, dy, dz) {
      if (dx === 0 && dy === 0 && dz === 0) return
      // 对于3DTileset，我们需要的结果是一个模型矩阵，那么平移就是计算一个世界坐标下的平移矩阵。
      // 获取中心点
      const origin = tileset.boundingSphere.center
      // 以该点建立ENU坐标系
      const toWorldMatrix = Transforms.eastNorthUpToFixedFrame(origin)
      // 该坐标系下平移后的位置
      const translatePosition = new Cartesian3(dx, dy, dz)
      // 获取平移后位置的世界坐标
      const worldPosition = Matrix4.multiplyByPoint(toWorldMatrix, translatePosition, new Cartesian3())
      // 计算世界坐标下的各个平移量
      const offset = Cartesian3.subtract(worldPosition, origin, new Cartesian3())
      // 从世界坐标下的平移量计算世界坐标的平移矩阵
      const translateMatrix = Matrix4.fromTranslation(offset)
      // 应用平移矩阵。这里应该与原本的模型矩阵点乘，而不是直接赋值
      tileset.modelMatrix = Matrix4.multiply(translateMatrix, tileset.modelMatrix, new Matrix4())
    }


    /**基于本地的ENU坐标系的缩放，也就是垂直于地表向上为Z，东为X，北为Y
     * @param tileset Cesium3DTileset
     * @param sx x轴缩放倍数
     * @param sy y轴缩放倍数
     * @param sz z轴缩放倍数
     */
    function scale(tileset, sx, sy, sz) {
      if (sx <= 0 || sy <= 0 || sz <= 0) throw Error('缩放倍数必须大于0')
      if (sx === 1 && sy === 1 && sz === 1) return
      // 具体步骤是将3DTileset先转为ENU坐标系，再在ENU坐标系下计算缩放后的结果，再转回世界坐标系。一个步骤代表一个矩阵
      // 获取中心点。
      const origin = tileset.boundingSphere.center
      // 以该点建立ENU坐标系
      const toWorldMatrix = Transforms.eastNorthUpToFixedFrame(origin)
      // 获取ENU矩阵的逆矩阵。也就是可以将世界坐标重新转为ENU坐标系的矩阵
      const toLocalMatrix = Matrix4.inverse(toWorldMatrix, new Matrix4())
      // 计算缩放矩阵
      const scaleMatrix = Matrix4.fromScale(new Cartesian3(sx, sy, sz))
      // ENU坐标系下的结果矩阵
      const localResultMatrix = Matrix4.multiply(scaleMatrix, toLocalMatrix, new Matrix4())
      // 世界坐标系下的结果矩阵
      const worldResultMatrix = Matrix4.multiply(toWorldMatrix, localResultMatrix, new Matrix4())
      // 应用结果
      tileset.modelMatrix = Matrix4.multiply(worldResultMatrix, tileset.modelMatrix, new Matrix4())
    }



    /**基于本地的ENU坐标系的旋转，也就是垂直于地表向上为Z，东为X，北为Y
     * @param tileset Cesium3DTileset
     * @param rx 绕X轴旋转的角度。单位：度
     * @param ry 绕Y轴旋转的角度。单位：度
     * @param rz 绕Z轴旋转的角度。单位：度
     */
    function rotate(tileset, rx, ry, rz) {
      if (rx === 0 && ry === 0 && rz === 0) return
      // 获取中心点。
      const origin = tileset.boundingSphere.center
      // 以该点建立ENU坐标系
      const toWorldMatrix = Transforms.eastNorthUpToFixedFrame(origin)
      // 获取ENU矩阵的逆矩阵。也就是可以将世界坐标重新转为ENU坐标系的矩阵
      const toLocalMatrix = Matrix4.inverse(toWorldMatrix, new Matrix4())
      // 计算旋转矩阵
      const rotateMatrix = Matrix4.clone(Matrix4.IDENTITY)
      if (rx !== 0) {
        const rotateXMatrix = Matrix4.fromRotation(Matrix3.fromRotationX(CesiumMath.toRadians(rx)))
        Matrix4.multiply(rotateXMatrix, rotateMatrix, rotateMatrix)
      }
      if (ry !== 0) {
        const rotateYMatrix = Matrix4.fromRotation(Matrix3.fromRotationY(CesiumMath.toRadians(ry)))
        Matrix4.multiply(rotateYMatrix, rotateMatrix, rotateMatrix)
      }
      if (rz !== 0) {
        const rotateZMatrix = Matrix4.fromRotation(Matrix3.fromRotationZ(CesiumMath.toRadians(rz)))
        Matrix4.multiply(rotateZMatrix, rotateMatrix, rotateMatrix)
      }
      // ENU坐标系下的结果矩阵
      const localResultMatrix = Matrix4.multiply(rotateMatrix, toLocalMatrix, new Matrix4())
      // 世界坐标系下的结果矩阵
      const worldResultMatrix = Matrix4.multiply(toWorldMatrix, localResultMatrix, new Matrix4())
      // 应用结果
      tileset.modelMatrix = Matrix4.multiply(worldResultMatrix, tileset.modelMatrix, new Matrix4())
    }


    function moveModel(tileset, longitude, latitude, height) {
      //计算世界坐标系中的目标位置offset
      var cartographic = new Cesium.Cartographic.fromCartesian(
        tileset.boundingSphere.center
      );
      var offset = Cesium.Cartesian3.fromDegrees(longitude, latitude, cartographic.height + height);

      //将模型位移至地心
      const origin = tileset.boundingSphere.center;
      const originMatrix = tileset.modelMatrix;//模型的初始变换矩阵
      const backToEarthCenter = new Cesium.Cartesian3(-origin.x, -origin.y, -origin.z);//初始位置到地心的位移向量
      let backToEarthCenterMatrix = Cesium.Matrix4.fromTranslation(backToEarthCenter);//初始位置到地心的变换矩阵
      Cesium.Matrix4.multiply(backToEarthCenterMatrix, originMatrix, backToEarthCenterMatrix);//移动模型到地心的矩阵

      // 旋转模型使得Z轴与世界坐标Z轴重合
      let arrowX = new Cesium.Cartesian3(1, 0, 0);
      let arrowZ = new Cesium.Cartesian3(0, 0, 1);
      let angleToXZ = Cesium.Cartesian3.angleBetween(arrowX, new Cesium.Cartesian3(origin.x, origin.y, 0));//局部Z轴在世界坐标系XY平面上投影到X轴角度，即绕Z顺时针旋转这个角度可以到XZ平面上
      let angleToZ = Cesium.Cartesian3.angleBetween(origin, arrowZ);//然后绕Y轴顺时针旋转此角度可使得Z轴与世界坐标系Z轴重合
      const rotationAngleToXZ = Cesium.Matrix3.fromRotationZ((origin.y > 0 ? -1 : +1) * angleToXZ);//绕Z轴旋转的Matrix3矩阵，正角度逆时针旋转
      const rotationAngleToZ = Cesium.Matrix3.fromRotationY(-angleToZ);//绕Y轴旋转的Matrix3矩阵，负角度顺时针旋转
      let rotationAngleToZMatrix = Cesium.Matrix3.multiply(rotationAngleToZ, rotationAngleToXZ, new Cesium.Matrix3);//连续旋转的Matrix3矩阵，即先绕Z轴旋转，后绕Y旋转的矩阵。
      rotationAngleToZMatrix = Cesium.Matrix4.fromRotationTranslation(rotationAngleToZMatrix);//连续旋转的Matrix4矩阵
      Cesium.Matrix4.multiply(rotationAngleToZMatrix, backToEarthCenterMatrix, rotationAngleToZMatrix);//将移动至地心模型，旋转至Z轴重合的矩阵

      // 旋转模型使得X，Y轴与世界坐标X，Y轴重合
      const rotationZ = Cesium.Matrix3.fromRotationZ(-Math.PI / 2); // 绕Z轴旋转90°的Matrix3变换矩阵
      let rotationMatrix = Cesium.Matrix4.fromRotationTranslation(rotationZ); // 绕Z轴旋转90°的Matrix4变换矩阵
      Cesium.Matrix4.multiply(rotationMatrix, rotationAngleToZMatrix, rotationMatrix);//将移动至地心模型的物体坐标系，旋转到与世界坐标系重合的矩阵

      //在地心位置，旋转物体坐标系和世界坐标系重合的模型，使得与目标坐标系重合
      const offsetToWorldMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(offset);//获取到以目标位置为原点,的eastNorthUp局部坐标系的变换矩阵
      const backToEarthCenterOffset = new Cesium.Cartesian3(-offset.x, -offset.y, -offset.z);//目标位置到地心的位移向量
      let backToEarthCenterMatrixOffset = Cesium.Matrix4.fromTranslation(backToEarthCenterOffset);//目标位置到地心的变换矩阵
      Cesium.Matrix4.multiply(backToEarthCenterMatrixOffset, offsetToWorldMatrix, backToEarthCenterMatrixOffset);//获得从世界坐标系旋转至目标坐标系的旋转矩阵（只有旋转，没有位移）
      Cesium.Matrix4.multiply(backToEarthCenterMatrixOffset, rotationMatrix, backToEarthCenterMatrixOffset);//将移动至地心模型的物体坐标系，旋转到与目标坐标系重合的矩阵（完成模型的最终旋转，没有位移）

      //移动到目标位置
      const backToOriginMatrix = Cesium.Matrix4.fromTranslation(offset);//地心到目标位置位移向量
      const lastMatrix = Cesium.Matrix4.multiply(backToOriginMatrix, backToEarthCenterMatrixOffset, new Cesium.Matrix4());//最终矩阵，即将地心位置的模型移动到目标位置（完成模型的最终旋转，最终位移）
      console.log('最终变换矩阵', lastMatrix);
      return lastMatrix //返回最终变换矩阵
    }
  </script>
</body>

</html>